package organizer.hb;

import java.io.File;
import java.util.List;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import organizer.DepthFirstAlgorithm;
import organizer.Duplicate;
import organizer.FileNodeReader;
import organizer.IFileMetadata;
import organizer.INodeVisitor;
import organizer.IOrganizerService;
import organizer.IProgressMonitor;
import organizer.MetadataComparator;
import organizer.NodeVisitorAdapter;

/**
 *
 * @author dawong
 */
public class HibernateOrganizerService implements IOrganizerService {
    
    private static final Logger log = Logger.getLogger(HibernateOrganizerService.class);

    public HibernateOrganizerService() {
        HibernateUtil.INSTANCE.createDatabase();
        report();
    }

    public int scanFiles(List<File> sourceDirs, final IProgressMonitor progress) {
        log.info("counting files");
        clearList();
        CounterVisitor counter = new CounterVisitor();
        DepthFirstAlgorithm<File> countAlg = new DepthFirstAlgorithm<File>(new FileNodeReader());
        for (File srcDir : sourceDirs) {
            countAlg.visit(srcDir, counter, progress);
        }
        int count = counter.getCount();
        if (progress != null) {
            progress.start(count);
            progress.showMessage("Scanning files");
        }
        DepthFirstAlgorithm<File> alg = new DepthFirstAlgorithm<File>(new FileNodeReader());
        log.info("scanning files");
        Session session = HibernateUtil.INSTANCE.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        final Session f_session = session;
        try {
            MetadataDao metadataDao = new MetadataDao(session);
            final MetadataList metadataList = new MetadataList(metadataDao);
            final FileRepository fileRepository = new FileRepository(metadataDao);
            for (final File srcDir : sourceDirs) {
                INodeVisitor<File> visitor = new NodeVisitorAdapter<File>() {
                    
                    int count = 0;

                    @Override
                    public void visit(File file) {
                        if (!file.isDirectory()) {
                            log.trace("adding file: " + file.getAbsolutePath());
                            IFileMetadata metadata = fileRepository.getMetadata(file);
                            metadataList.add(metadata);
                            if (progress != null)
                                progress.increment(1);
                            if (count > 100) {
                                f_session.flush();
                                f_session.clear();
                                count = 0;
                            }
                            count++;
                        }
                    }

                };
                if (progress != null && progress.isCanceled())
                    break;
                alg.visit(srcDir, visitor, progress);
            }
        } catch (Exception e) {
            log.debug(null, e);
            session.getTransaction().rollback();
            session = null;
        } finally {
            if (session != null)
                session.getTransaction().commit();
            if (progress != null)
                progress.stop();
        }
        return count;
    }
    
    private void clearList() {
        Session session = HibernateUtil.INSTANCE.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        MetadataDao metadataDao = new MetadataDao(session);
        MetadataList metadataList = new MetadataList(metadataDao);
        metadataList.clear();
        session.getTransaction().commit();
    }

    private static class CounterVisitor extends NodeVisitorAdapter<File> {

        private int count = 0;

        @Override
        public void visit(File node) {
            if (!node.isDirectory()) {
                count++;
            }
        }

        public int getCount() {
            return count;
        }

    }

    public void calcDuplicates(IProgressMonitor progress) {
        log.info("calc duplicates");
        Session session = HibernateUtil.INSTANCE.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        try {
            MetadataDao metadataDao = new MetadataDao(session);
            MetadataList metadataList = new MetadataList(metadataDao);
            metadataList.sort(new MetadataComparator());
        } catch (Exception e) {
            log.debug(null, e);
            session.getTransaction().rollback();
            session = null;
        } finally {
            if (session != null)
                session.getTransaction().commit();
        }
    }
    
    public List<Duplicate> getDuplicates(IProgressMonitor progress) {
        log.info("finding duplicates");
        List<Duplicate> dupList = null;
        Scanner scanner = new Scanner();
        SessionManager manager = new SessionManager(scanner, progress, 100);
        try {
            dupList = scanner.getDuplicates(manager, manager);
        } catch (Exception e) {
            log.debug(null, e);
            manager.rollback();
        } finally {
            manager.commit();
        }
        log.info("finished finding duplicates");
        report();
        return dupList;
    }
    
    private void report() {
        Session session = HibernateUtil.INSTANCE.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        try {
            MetadataDao dao = new MetadataDao(session);
            int hashedFiles = dao.getHashedFileCount();
            int scannedFiles = dao.size();
            int totalFiles = dao.getTotalFileCount();
            log.info(hashedFiles + " out of " + scannedFiles + " files were hashed.");
            log.info(totalFiles + " files in database");
        } catch (Exception e) {
            log.debug(null, e);
            session.getTransaction().rollback();
            session = null;
        } finally {
            if (session != null)
                session.getTransaction().commit();
        }
    }
    
    /**
     * Flush and clear session periodically.  Do not flush/clear when in the middle
     * of a scan group (all files of the same size).  Do not flush until a minimum
     * number of objects are in the cache.
     */
    private static class SessionManager implements IProgressMonitor, IScannerListener {
        
        private Scanner scanner;
        private Session session;
        private IProgressMonitor progress;
        private int flushSize;

        public SessionManager(Scanner scanner, IProgressMonitor progress, int flushSize) {
            this.scanner = scanner;
            this.progress = progress;
            this.flushSize = flushSize;
            init();
        }
        
        private int count = 0;
        private boolean inGroup = false;

        public void start(int size) {
            progress.start(size);
        }

        public void increment(int incr) {
            progress.increment(incr);
            count++;
            requestFlush();
        }

        public void stop() {
            progress.stop();
        }

        public boolean isCanceled() {
            return progress.isCanceled();
        }

        public void showMessage(String message) {
            progress.showMessage(message);
        }

        public void dupGroupStarted() {
            inGroup = true;
        }

        public void dupGroupFinished() {
            inGroup = false;
            requestFlush();
        }
        
        private void init() {
            session = HibernateUtil.INSTANCE.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            MetadataDao metadataDao = new MetadataDao(session);
            MetadataList metadataList = new MetadataList(metadataDao);
            FileRepository fileRepository = new FileRepository(metadataDao);
            scanner.setMetadataList(metadataList);
            scanner.setRepository(fileRepository);
            count = 0;
        }
        
        private void requestFlush() {
            if (!inGroup && count > flushSize) {
                commit();
                init();
            }
        }
        
        public void commit() {
            session.getTransaction().commit();
        }
        
        public void rollback() {
            session.getTransaction().rollback();
        }
        
    }

}
